package service

import (
	"context"
	"encoding/json"
	"strconv"
	"time"

	"wxpay/pkg/util"

	"gorm.io/gorm"

	"wxpay/internal/dto"
	"wxpay/internal/model"
	"wxpay/pkg/merrors"
	mchApi "wxpay/pkg/wechat_api"
)

// 特约商户进件申请
func (s *applymentService) SubMchApplymentSubmit(ctx context.Context, apply *dto.SubMchApplymentDTO) (*dto.SubMchApplymentDTO, error) {
	const op merrors.Op = "applymentService.SubMchApplymentSubmit"
	r, fErr := s.d.Applyment().QueryByBizApplyNo(ctx, s.d.GetTx(ctx), apply.BizApplyNo)
	client, e := NewMchService(s.d).BuildMchApiClient(ctx, apply.MchID)
	if fErr == nil {
		if !r.CanReApply() {
			return dto.ConvertSubMchApplyment(r), nil
		}
		apply.ID = r.ID
		apply.ApplyNo = r.ApplyNo
		return s.submitSubMchApplyment(ctx, client, apply)
	}
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		apply.ApplyNo = GenApplyNo()

		if e != nil {
			return nil, merrors.E(op, merrors.Invalid, e)
		}
		apply.CreateTime = util.GetNow()
		return s.submitSubMchApplyment(ctx, client, apply)
	}
	return nil, fErr
}

// 特约商户进件重新提交
func (s *applymentService) SubMchApplymentReSubmit(ctx context.Context, apply *dto.SubMchApplymentDTO) (*dto.SubMchApplymentDTO, error) {
	const op merrors.Op = "applymentService.SubMchApplymentReSubmit"
	r, fErr := s.d.Applyment().QueryByBizApplyNo(ctx, s.d.GetTx(ctx), apply.BizApplyNo)
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		return nil, merrors.E(merrors.NotExist, merrors.ErrNotFound, "找不到申请单")
	}
	if fErr != nil {
		return nil, fErr
	}

	apply.ID = r.ID
	apply.ApplyNo = r.ApplyNo
	client, e := NewMchService(s.d).BuildMchApiClient(ctx, apply.MchID)
	if e != nil {
		return nil, merrors.E(op, merrors.Invalid, e)
	}
	return s.submitSubMchApplyment(ctx, client, apply)
}

// 特约商户进件查询
func (s *applymentService) SubMchApplymentQuery(ctx context.Context, brandId, bizApplyNo string) (*dto.SubMchApplymentDTO, error) {
	const op = "applymentService.SubMchApplymentQuery"
	r, fErr := s.d.Applyment().QueryByBizApplyNo(ctx, s.d.GetTx(ctx), bizApplyNo)
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		return &dto.SubMchApplymentDTO{
			ApplyState:     "NOT_APPLIED",
			ApplyStateDesc: "未找到进件信息",
		}, nil
	}
	if fErr != nil {
		return nil, fErr
	}
	if r.BrandID != brandId {
		return nil, merrors.E(op, merrors.Permission, merrors.ErrPermission, "没有权限查看此订单")
	}
	applyDto := dto.ConvertSubMchApplyment(r)
	// 检查是否终态，不是的终态强制检查一次
	if !r.SubMchIsFinalState() {
		client, err := NewMchService(s.d).BuildMchApiClient(ctx, r.MchID)
		if err != nil {
			return nil, err
		}
		applyDto, err = s.querySubMchApplyment(ctx, client, applyDto)
		err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
			if r.IsNewState(applyDto.ApplyState) {
				m := dto.AdaptSubApplyment(applyDto)
				m.SubMchUpdateState()
				if err = s.d.Applyment().Save(ctx, tx, m); err != nil {
					return err
				}
			}
			return nil
		})
		if err != nil {
			return nil, err
		}
	}
	return applyDto, nil
}

func (s *applymentService) ExecuteSubApplymentQuery(ctx context.Context, task *model.SubMchApplymentQueryTask) error {
	m, err := s.d.Applyment().QueryByApplyNo(ctx, s.d.GetTx(ctx), task.ApplyNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		return nil
	}
	if err != nil {
		return err
	}
	if m.IsFinalState() {
		return nil
	}
	// 调用查询接口并更新状态
	err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		applyment, err := s.d.Lock().LockApplyment(ctx, tx, m.ID)
		if err != nil {
			return err
		}
		client, err := NewMchService(s.d).BuildMchApiClient(ctx, applyment.MchID)
		if err != nil {
			return err
		}
		req := mchApi.SubMchApplymentQueryByOutRequestNoRequest{BusinessCode: applyment.ApplyNo}
		resp, err := client.SubMchApplymentQueryByOutRequestNo(ctx, req)
		if err != nil {
			return err
		}
		if applyment.IsNewState(resp.ApplymentState) {
			audits := make(model.AuditDetails, len(resp.AuditDetail))
			for idx, au := range resp.AuditDetail {
				audits[idx] = model.AuditDetail{
					ParamName:    au.Field,
					RejectReason: au.RejectReason,
				}
			}
			applyment.ApplyState = util.AdaptNullString(resp.ApplymentState)
			applyment.SignURL = util.AdaptNullString(resp.SubMchid)
			applyment.SubMchUpdateState()
			applyment.UpdateState(resp.ApplymentState, resp.ApplymentStateMsg, resp.SignUrl, resp.SubMchid, nil, &audits)
			if err = s.d.Applyment().Save(ctx, tx, applyment); err != nil {
				return err
			}
			if err = s.createSubMchApplymentNotification(ctx, tx, applyment); err != nil {
				return err
			}
		}
		if !applyment.SubMchIsFinalState() {
			if err = s.createSubMchApplymentQueryTask(ctx, tx, applyment); err != nil {
				return err
			}
		}
		return nil
	})
	return err
}

// 创建特约商户微信进件查询任务
func (s *applymentService) createSubMchApplymentQueryTask(ctx context.Context, tx *gorm.DB, apply *model.Applyment) error {
	payload := model.NewSubMchApplymentQueryTask(apply)
	ps, _ := json.Marshal(&payload)
	// 延后1个小时执行
	msg := model.NewMessage(model.TopicSubMchApplymentQuery, ps, time.Hour)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 创建特约商户进件结果通知
func (s *applymentService) createSubMchApplymentNotification(ctx context.Context, tx *gorm.DB, apply *model.Applyment) error {
	payload := model.NewApplymentNotification(apply)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicSubMchApplymentNotify, ps, 0)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

func subMchApplymentInfoToSubmitRequest(apply *dto.SubMchApplymentDTO) mchApi.SubMchApplymentRequest {
	r := mchApi.SubMchApplymentRequest{
		BusinessCode:    apply.ApplyNo,
		ContactInfo:     nil,
		SubjectInfo:     nil,
		BusinessInfo:    nil,
		SettlementInfo:  nil,
		BankAccountInfo: nil,
		AdditionInfo:    nil,
	}
	if apply.SubApplymentInfo.ContactInfoDTO != nil {
		con := apply.SubApplymentInfo.ContactInfoDTO
		r.ContactInfo = &mchApi.SubContactInfo{
			ContactName:     con.ContactName,
			ContactIDNumber: con.ContactIdNumber,
			MobilePhone:     con.MobilePhone,
			ContactEmail:    con.ContactEmail,
		}
	}
	if apply.SubApplymentInfo.SubjectInfoDTO != nil {
		sub := apply.SubApplymentInfo.SubjectInfoDTO
		r.SubjectInfo = &mchApi.SubjectInfo{
			SubjectType:           sub.SubjectType,
			BusinessLicenseInfo:   nil,
			CertificateInfo:       nil,
			OrganizationInfo:      nil,
			CertificateLetterCopy: sub.CertificateLetterCopy,
			IdentityInfo:          nil,
			UboInfo:               nil,
			MicroBizInfo:          nil,
		}
		if sub.BusinessLicenseInfoDTO != nil {
			bus := sub.BusinessLicenseInfoDTO
			r.SubjectInfo.BusinessLicenseInfo = &mchApi.SubBusinessLicenseInfo{
				LicenseCopy:   bus.LicenseCopy,
				LicenseNumber: bus.LicenseNumber,
				MerchantName:  bus.MerchantName,
				LegalPerson:   bus.LegalPerson,
			}
		}
		if sub.CertificateInfoDTO != nil {
			cert := sub.CertificateInfoDTO
			r.SubjectInfo.CertificateInfo = &mchApi.CertificateInfo{
				CertCopy:       cert.CertCopy,
				CertType:       cert.CertType,
				CertNumber:     cert.CertNumber,
				MerchantName:   cert.MerchantName,
				CompanyAddress: cert.CompanyAddress,
				LegalPerson:    cert.LegalPerson,
				PeriodBegin:    cert.PeriodBegin,
				PeriodEnd:      cert.PeriodEnd,
			}
		}
		if sub.OrganizationInfoDTO != nil {
			org := sub.OrganizationInfoDTO
			r.SubjectInfo.OrganizationInfo = &mchApi.OrganizationInfo{
				OrganizationCopy: org.OrganizationCopy,
				OrganizationCode: org.OrganizationCode,
				OrgPeriodBegin:   org.OrgPeriodBegin,
				OrgPeriodEnd:     org.OrgPeriodEnd,
			}
		}
		if sub.IdentityInfoDTO != nil {
			iden := sub.IdentityInfoDTO
			r.SubjectInfo.IdentityInfo = &mchApi.IdentityInfo{
				IDDocType: iden.IdDocType,
				Owner:     iden.Owner,
			}
			if iden.IdDocInfoDTO != nil {
				r.SubjectInfo.IdentityInfo.IDDocInfo = &mchApi.SubIDDocInfo{
					IDDocCopy:      iden.IdDocInfoDTO.IdDocCopy,
					IDDocName:      iden.IdDocInfoDTO.IdDocName,
					IDDocNumber:    iden.IdDocInfoDTO.IdDocNumber,
					DocPeriodBegin: iden.IdDocInfoDTO.DocPeriodBegin,
					DocPeriodEnd:   iden.IdDocInfoDTO.DocPeriodEnd,
				}
			}
			if iden.IdCardInfoDTO != nil {
				r.SubjectInfo.IdentityInfo.IDCardInfo = &mchApi.SubIDCardInfo{
					IDCardCopy:      iden.IdCardInfoDTO.IdCardCopy,
					IDCardNational:  iden.IdCardInfoDTO.IdCardNational,
					IDCardName:      iden.IdCardInfoDTO.IdCardName,
					IDCardNumber:    iden.IdCardInfoDTO.IdCardNumber,
					CardPeriodBegin: iden.IdCardInfoDTO.CardPeriodBegin,
					CardPeriodEnd:   iden.IdCardInfoDTO.CardPeriodEnd,
				}
			}
		}
		if sub.MicroBizInfoDTO != nil {
			micro := sub.MicroBizInfoDTO
			r.SubjectInfo.MicroBizInfo = &mchApi.MicroBizInfo{
				MicroBizType:    micro.MicroBizType,
				MicroStoreInfo:  nil,
				MicroMobileInfo: nil,
				MicroOnlineInfo: nil,
			}
			if micro.MicroStoreInfoDTO != nil {
				r.SubjectInfo.MicroBizInfo.MicroStoreInfo = &mchApi.MicroStoreInfo{
					MicroName:        micro.MicroStoreInfoDTO.MicroName,
					MicroAddressCode: micro.MicroStoreInfoDTO.MicroAddressCode,
					MicroAddress:     micro.MicroStoreInfoDTO.MicroAddress,
					StoreEntrancePic: micro.MicroStoreInfoDTO.StoreEntrancePic,
					MicroIndoorCopy:  micro.MicroStoreInfoDTO.MicroIndoorCopy,
					StoreLongitude:   micro.MicroStoreInfoDTO.StoreLongitude,
					StoreLatitude:    micro.MicroStoreInfoDTO.StoreLatitude,
				}
			}
			if micro.MicroMobileInfoDTO != nil {
				r.SubjectInfo.MicroBizInfo.MicroMobileInfo = &mchApi.MicroMobileInfo{
					MicroMobileName:    micro.MicroMobileInfoDTO.MicroMobileName,
					MicroMobileCity:    micro.MicroMobileInfoDTO.MicroMobileCity,
					MicroMobileAddress: micro.MicroMobileInfoDTO.MicroMobileAddress,
					MicroMobilePics:    micro.MicroMobileInfoDTO.MicroMobilePics,
				}
			}
			if micro.MicroOnlineInfoDTO != nil {
				r.SubjectInfo.MicroBizInfo.MicroOnlineInfo = &mchApi.MicroOnlineInfo{
					MicroOnlineStore: micro.MicroOnlineInfoDTO.MicroOnlineStore,
					MicroEcName:      micro.MicroOnlineInfoDTO.MicroEcName,
					MicroQrcode:      micro.MicroOnlineInfoDTO.MicroQrcode,
					MicroLink:        micro.MicroOnlineInfoDTO.MicroLink,
				}
			}
		}
		if sub.UboInfoDTO != nil {
			ubo := sub.UboInfoDTO
			r.SubjectInfo.UboInfo = &mchApi.UboInfo{
				IDType:         ubo.IdType,
				IDCardCopy:     ubo.IdCardCopy,
				IDCardNational: ubo.IdCardNational,
				IDDocCopy:      ubo.IdDocCopy,
				Name:           ubo.Name,
				IDNumber:       ubo.IdNumber,
				IDPeriodBegin:  ubo.IdPeriodBegin,
				IDPeriodEnd:    ubo.IdPeriodEnd,
			}
		}
	}
	if apply.SubApplymentInfo.BusinessInfoDTO != nil {
		bus := apply.SubApplymentInfo.BusinessInfoDTO
		r.BusinessInfo = &mchApi.BusinessInfo{
			MerchantShortname: bus.MerchantShortname,
			ServicePhone:      bus.ServicePhone,
			SalesInfo:         nil,
		}
		if bus.SalesInfoDTO != nil {
			sale := bus.SalesInfoDTO
			r.BusinessInfo.SalesInfo = &mchApi.SalesInfo{
				SalesScenesType: sale.SalesScenesType,
			}
			if bus.SalesInfoDTO.BizStoreInfo != nil {
				r.BusinessInfo.SalesInfo.BizStoreInfo = &mchApi.BizStoreInfo{
					BizStoreName:     sale.BizStoreInfo.BizStoreName,
					BizAddressCode:   sale.BizStoreInfo.BizAddressCode,
					BizStoreAddress:  sale.BizStoreInfo.BizStoreAddress,
					StoreEntrancePic: sale.BizStoreInfo.StoreEntrancePic,
					IndoorPic:        sale.BizStoreInfo.IndoorPic,
					BizSubAppid:      sale.BizStoreInfo.BizSubAppId,
				}
			}
			if bus.SalesInfoDTO.MpInfo != nil {
				r.BusinessInfo.SalesInfo.MpInfo = &mchApi.MpInfo{
					MpAppid:    bus.SalesInfoDTO.MpInfo.MpAppId,
					MpSubAppid: bus.SalesInfoDTO.MpInfo.MpSubAppId,
					MpPics:     bus.SalesInfoDTO.MpInfo.MpPics,
				}
			}
			if bus.SalesInfoDTO.MiniProgramInfo != nil {
				r.BusinessInfo.SalesInfo.MiniProgramInfo = &mchApi.MiniProgramInfo{
					MiniProgramAppid:    bus.SalesInfoDTO.MiniProgramInfo.MiniProgramAppId,
					MiniProgramSubAppid: bus.SalesInfoDTO.MiniProgramInfo.MiniProgramSubAppId,
					MiniProgramPics:     bus.SalesInfoDTO.MiniProgramInfo.MiniProgramPics,
				}
			}
			if bus.SalesInfoDTO.AppInfo != nil {
				r.BusinessInfo.SalesInfo.AppInfo = &mchApi.AppInfo{
					AppAppid:    bus.SalesInfoDTO.AppInfo.AppAppId,
					AppSubAppid: bus.SalesInfoDTO.AppInfo.AppSubAppId,
					AppPics:     bus.SalesInfoDTO.AppInfo.AppPics,
				}
			}
			if bus.SalesInfoDTO.WebInfo != nil {
				r.BusinessInfo.SalesInfo.WebInfo = &mchApi.WebInfo{
					Domain:           bus.SalesInfoDTO.WebInfo.Domain,
					WebAuthorisation: bus.SalesInfoDTO.WebInfo.WebAuthorisation,
					WebAppID:         bus.SalesInfoDTO.WebInfo.WebAppId,
				}
			}
			if bus.SalesInfoDTO.WeworkInfo != nil {
				r.BusinessInfo.SalesInfo.WeworkInfo = &mchApi.WeworkInfo{
					SubCorpID:  bus.SalesInfoDTO.WeworkInfo.SubCorpId,
					WeworkPics: bus.SalesInfoDTO.WeworkInfo.WeworkPics,
				}
			}
		}
	}
	if apply.SubApplymentInfo.SettlementInfoDTO != nil {
		sett := apply.SubApplymentInfo.SettlementInfoDTO
		r.SettlementInfo = &mchApi.SettlementInfo{
			SettlementID:        sett.SettlementId,
			QualificationType:   sett.QualificationType,
			Qualifications:      sett.Qualifications,
			ActivitiesID:        sett.ActivitiesId,
			ActivitiesRate:      sett.ActivitiesRate,
			ActivitiesAdditions: sett.ActivitiesAdditions,
		}
	}
	if apply.SubApplymentInfo.BankAccountInfoDTO != nil {
		bank := apply.SubApplymentInfo.BankAccountInfoDTO
		r.BankAccountInfo = &mchApi.BankAccountInfo{
			BankAccountType: bank.BankAccountType,
			AccountBank:     bank.AccountBank,
			AccountName:     bank.AccountName,
			BankAddressCode: bank.BankAddressCode,
			BankBranchID:    bank.BankBranchID,
			BankName:        bank.BankName,
			AccountNumber:   bank.AccountNumber,
		}
	}
	if apply.SubApplymentInfo.AdditionInfoDTO != nil {
		add := apply.SubApplymentInfo.AdditionInfoDTO
		r.AdditionInfo = &mchApi.AdditionInfo{
			LegalPersonCommitment: add.LegalPersonCommitment,
			LegalPersonVideo:      add.LegalPersonVideo,
			BusinessAdditionPics:  add.BusinessAdditionPics,
			BusinessAdditionMsg:   add.BusinessAdditionMsg,
		}
	}
	return r
}

func (s *applymentService) submitSubMchApplyment(ctx context.Context, client mchApi.MerchantApiClient, apply *dto.SubMchApplymentDTO) (*dto.SubMchApplymentDTO, error) {
	const op = "applymentService.submitSubMchApplyment"
	m := dto.AdaptSubApplyment(apply)
	if err := m.Validate(); err != nil {
		return nil, merrors.E(op, merrors.Invalid, merrors.InvalidArgument, err)
	}
	// 提交申请
	req := subMchApplymentInfoToSubmitRequest(apply)
	res, err := client.SubMchApplymentSubmit(ctx, req)
	if err != nil {
		return nil, err
	}
	apply.WxApplymentID = strconv.FormatInt(res.ApplymentID, 10)
	// 查询进件详情
	applyDto, err := s.querySubMchApplyment(ctx, client, apply)
	if err != nil {
		return nil, err
	}
	m = dto.AdaptSubApplyment(applyDto)
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		m.SubMchUpdateState()
		if err = s.d.Applyment().Save(ctx, tx, m); err != nil {
			return err
		}
		if err = s.createSubMchApplymentQueryTask(ctx, tx, m); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return nil, err
	}
	return dto.ConvertSubMchApplyment(m), nil
}

func (s *applymentService) querySubMchApplyment(ctx context.Context, client mchApi.MerchantApiClient, apply *dto.SubMchApplymentDTO) (*dto.SubMchApplymentDTO, error) {
	if apply == nil {
		return nil, nil
	}
	req := mchApi.SubMchApplymentQueryByOutRequestNoRequest{BusinessCode: apply.ApplyNo}
	res, err := client.SubMchApplymentQueryByOutRequestNo(ctx, req)
	if err != nil {
		return nil, err
	}
	apply.SubMchID = res.SubMchid
	apply.SignURL = res.SignUrl
	apply.ApplyState = res.ApplymentState
	apply.ApplyStateDesc = apply.GetApplyStateDesc()
	audits := make(dto.AuditDetailDTO, len(res.AuditDetail))
	for idx, au := range res.AuditDetail {
		audits[idx] = struct {
			ParamName    string
			RejectReason string
		}{ParamName: au.Field, RejectReason: au.RejectReason}
	}
	apply.AuditDetail = audits
	return apply, nil
}
